home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Gekkan Dennou Club 147
/
Gekkan Dennou Club - 2000.8 Vol. 147 (Japan).7z
/
Gekkan Dennou Club - 2000.8 Vol. 147 (Japan) (Track 1).bin
/
docs
/
ippon
/
consist2.doc
< prev
next >
Wrap
Text File
|
2000-07-07
|
17KB
|
519 lines
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
要素研究2
────────────────────────────────────
▼ 始めに
ここでもシューティングゲームに必要な各要素の研究を行いますが、特にシュー
ティングゲームに特化した概念やテクニックについての解説を中心とします。
▼ 固定小数点
今回使用する画面モードは 256x256 ドットです。このことから一見座標は
char で足りそうですが、画面端の処理を考えると実はマイナスの座標も必要と
なってきます(画面左端から少しだけ見えているキャラクターを考えてみて下さ
い)。では signed short なら十分でしょうか?
これはゲームデザインにも関わってくる問題です。移動して表示するだけであ
れば signed short で十分ですし、実際、多くのゲームが座標を signed short
で表しています。
ただ、もう少し豊かな表現力=リアルなキャラクターの動きを追求したいので
あればもう少し精度が欲しくなります。例えば敵弾の移動ルーチン。敵弾が8方
向にしか移動しないのであれば signed short で十分ですが、これを16方向にす
るにはどうしたら良いのでしょう? 32方向では? 256方向では?
解決法としては、
・Xを2回足したら1回足すのようなカウンターを導入
・敵弾の移動ルーチンにラインを引くアルゴリズムを使う
・小数点の概念を導入
前2者のような解決法もあるのですが、ここでは簡単かつ高速な小数点の概念
を導入します。
M68000 では直接小数点は使えません。コプロセッサ MC68881/MC68882 を使用
するか、ソフトウェアで同等の処理を行うかの2つです。どちらにしても非常に
遅く、シューティングゲームで使えるようなシロモノではありません。そこで
今回は真面目に float や double を使わず固定小数点を使います。
固定小数点とは float や double といった浮動小数点と違い、小数点以下の
桁数が決まった小数点です。今回使う固定小数点は以下のようなものです。
・signed int の上 16bit を整数部、下 16bit を小数部と見なす
・数値は 65536 倍する(下駄を履かせる、という)
具体例を挙げると、今まで singed short で 1 だったものを signed int で
65536 とします。小数 0.5 は 32768 とします。32768+32768 = 65536 ですから
0.5+0.5 = 1 と一致します。もちろん 2 は 65536+65536 = 131072 となります。
画面に表示する時等、整数部が欲しくなった時は 65536 で割れば整数部が取
り出せます。65536 で割るのはシフトで済むので >>16 で高速に取り出せます。
>>16 はシフト命令ではなく swap 命令に展開されるのでわずか4クロックです。
更に高速なのはキャストを使って取り出す方法です。これについては例を挙げま
すので各自考えてみて下さい(このマクロはMPUのエンディアンに依存するの
で Intel のマシンに持っていくなんて人は注意)。
----
/* signed int の座標値 (p->lx,p->ly) から固定小数部(上位16bit)を取り出すマクロ */
#define SHORT_LX (*((short *) (&p->lx)))
#define SHORT_LY (*((short *) (&p->ly)))
----
このように固定小数点は見掛け上は小数が扱えても実際は signed int ですか
ら(signed short よりは遅いものの)高速に扱えます。
固定小数点が便利なのは敵弾の移動ルーチンだけではありません。キャラクター
の移動ルーチンで加速度を使った動きを実現したい場合、固定小数点は必須です。
加速度を使った動きをサポートすると画面上での動きがとてもリアルになるので
是非とも使いましょう。単純な等加速度運動だけでなく、空気抵抗やばね振動な
ども導入するとより一層リアルな動きが実現できます。
今回は整数 16bit + 小数 16bit の固定小数点を使いましたが、整数 12bit +
小数 4bit の固定小数点も考えられます。これなら 16bit で収まるため signed
short で済むのですが、下駄を外す時に >>4 というシフトが必要になり、これ
が遅いのではないか?と思い今回は採用しませんでした。個人的には精度もちょっ
と不足かな、と思いますし。ただ、状況によってはこちらの方が良い場合もある
かもしれないので検討する価値はあるかもしれません。なお、『男弾』『一本槍』
はともに 16bit+16bit 固定小数点です。
▼ SIN/COS テーブル
固定小数点の応用として敵弾移動ルーチンを考えてみます。敵弾の速度を v
とし、角度θで弾を撃った場合、xy 方向の速度 (vx,vy) は以下のようになりま
す。
(0,0) vx
┼────────→ X vx = v * cos θ
│\)θ | vy = v * sin θ
│ \ |
│ \ | ※X680x0 の画面なので下向きが正
│速度 v\ | なのに注意
│ \|
vy│---------敵弾の移動方向(右下へ)
↓
Y
sin,cos は数値関数として用意されていますが、シューティングゲーム中で使
うには遅いので予め計算しておいて固定小数値に直しておきます。このような予
め計算した数表の事をテーブルと呼び、高速化の常套手段として幅広く使われて
います。
角度θは360°で1周ではなく、256で一周という変な角度系を導入します。ま
た、X680x0 の画面で使うことからY方向は下向きを正とします。まとめると、
・右向き = 0
・下向き = 64
・左向き = 128
・上向き = 192
となります。1周256というのは即ち unsigned char で、こうしておくと 256
を越えた時に自動的に0に戻るので便利です。
例)240+32 = 16 となる
一周256で精度的に足りるのか?という疑問もおありでしょうが、『男弾』の
美しい弾幕を見ている限り十分足りているようです。
プログラミング例は以下の通りです。
----
typedef struct {
signed int x, y;
} VECTOR;
VECTOR xytable[256]; /* sin,cos テーブル */
for (i = 0; i < 256; i++) {
xytable[i].x = (signed int) (cos (2.0 * M_PI * (long) i / 256.0) * 65536.0 * ESHOT_SPEED);
xytable[i].y = (signed int) (sin (2.0 * M_PI * (long) i / 256.0) * 65536.0 * ESHOT_SPEED);
}
----
一度作ってしまえばあとは xyteble[i].x/xyteble[i].y で (vx,vy) が取り出
せます。面倒な三角関数の計算がテーブル参照一発です。
sin(90°-θ) = cosθ等の数式を使えばテーブルはちょっとだけ小さくなるの
ですが、横長画面モードで使う時の事を考慮し、今回は普通のテーブルにしてあ
ります。もし横長画面モード用のゲームであれば、テーブルの段階で縦横比を補
正してしまうとゲーム中に考慮しなくて済むので便利だと思います。
▼ 自機サーチと ATAN テーブル
前項は角度からXY方向の速度を求めるルーチンでしたが、座標から角度を求め
たい時があります。これは、
・敵が自機に向かって弾を撃つ
時に必要となります。下の図を見て下さい。
敵(0,0) px
┼────────→ X tanθ = py/px
│\)θ | θ = atan(py/px)
│ \ |
│ \ | ※X680x0 の画面なので下向きが正
│ \ | なのに注意
│ \|
py│---------自機(px,py)
↓
Y
敵が (0,0) にいるとします(いない場合は平行移動して考えます)。上記の
式から角度θ = atan(py/px) となります。また三角関数ですね。ここで
(px,py) の組を考えてみます。画面モードが 256x256 の場合、px も py もほぼ
0~255 の範囲に収まります。各256通りですね。そして得られる数値は角度
unsigned char ですから、全ての組み合わせをテーブルで持っても 256x256x1
= 65536 bytes で済みます。テーブルにしましょう。
----
for (i = 0; i < 256; i++) {
for (j = 0; j < 256; j++) {
pstable[i][j] = (unsigned char) (atan ((double) j / (double) i) * 256.0 / 2.0 / M_PI);
}
}
----
このテーブルを使えば敵と自機の座標の差から角度をテーブル参照一発で出す
ことができます。
注意点は以下の通りです。
・自機が右下にいると仮定しているので、上や左にいる時は適宜マイナ
スを付けて参照すること
・敵が画面外にいる時等、座標の差が255で収まらない時があるので、
その時も適宜調整すること。具体的には「座標の差がが255以上の時
は255と見なす」ようにする等。こうすると誤差が発生するが、ゲー
ムで使用するぶんには遠い時は照準が不正確になっても問題無い。
▼ 当たり判定
シューティングゲームで非常に重要なのが当たり判定。普段遊んでいるぶんに
は当たり前すぎて意識しない、空気のような処理ですが、いざ自分で作ってみる
と当たり判定は長年の歴史の間に積み重ねられたノウハウの固まりである事に気
が付きます。そのノウハウをここで明かしていきましょう。
・当たり判定は矩形が基本
当たり判定というのはつまるところ座標の比較です。自機の座標と敵の座標、
この2つを比較してある程度近ければ当たりと判定します。当たり判定の方式と
しては矩形・ビットマップ・円形・それらを組み合わせたもの4種類が知られて
います。ではそれぞれの特徴を見ていきましょう。
1)矩形
キャラクターの外観(グラフィックパターン)とは無関係に矩形(四
角形)で当たり判定を行うこと。「自機の矩形と敵の矩形が重なった時
が当たり」という処理。加算と比較だけで済むので高速な処理が可能。
自然なプレイ感覚的が得られるため(少し前までは)ほとんどのシュー
ティングゲームで採用されていた。
2)ビットマップ
キャラクターの外観(グラフィックパターン)通りに当たり判定を行
うこと。「自機のスプライトの透明でない部分と敵のスプライトの透明
でない部分が重なったら当たり」という処理。外観によっては「自機の
翼の先端にかすっただけで当たり」のような事があるためあまり用いら
れないが、背景との当たり判定等、特殊な用途では有効。
3)円形
キャラクターの外観(グラフィックパターン)とは無関係に円形で当
たり判定を行うこと。「自機の円と敵の円が重なった時が当たり」とい
う処理。乗算が必要なため、乗算の高速なMPUが必須。プレイ感覚的
には比較的自然。3次元のゲームでは1次元増やして球形で処理を行う
ことも。最近の主流はこれ(サイヴァリアとか)。
4)上記のものを組み合わせたもの
例えば触手のような形状の場合、関節1つ1つを矩形で判定し、それ
らを関節数ぶんだけ判定を行う。矩形だけでは対応し切れない形状の時
に有効。速度的には矩形よりやや遅い程度。
今回は X680x0 用であること、縦シューであることを考慮して1)矩形による
当たり判定を採用します。
矩形による当たり判定とは何か、は下の図を見ると一目瞭然です。
敵
+----+
| |
| +-----+自機ショット
+--|-+ |
| |
+-----+
敵を表す矩形と自機ショットを表す矩形が重なった時、「当たり」と判定され
ます。
当たり判定は2次元(3次元)による判定がほとんどですが、判り易くするた
め1次元の数直線上で考えていきましょう。2次元、3次元の場合は同じ処理を
Y方向、Z方向に対しても行えばOKです。
敵キャラのX座標を ex、当たり判定の幅を hx とし、当たり判定は左右対象
とします。自機ショットの座標を SX、当たり判定の幅を HX とし、当たり判定
は左右対象とします。下の数直線をご覧下さい。
ex-hx ex+hx
----------+---+---+---------- 敵キャラ
ex
SX-HX SX+HX
------------+-+-+------------ 自機ショット
sx
ex-hx ex+hx
----------+---+---+---------- 敵キャラ
| ex |
| |
| |
SX+HX |
------+-+-+------------------ 自機ショット
SX | (SX+HX) >= (ex-hx) なら当たり
|
SX-HX
------------------+-+-+------ 自機ショット
SX (SX-HX) <= (ex+hx) なら当たり
結果、
if (((SX+HX) >= (ex-hx))
&& ((SX-HX) <= (ex+hx)))
を満たせば当たりです。
「(SX+HX) >= (ex-hx) なら当たり」は「自機ショットの右端が敵キャラの左
端より右だったら当たり」、「(SX-HX) <= (ex+hx) なら当たり」は「自機ショッ
トの左端が敵キャラの右端より左だったら当たり」と読みかえると判り易いでしょ
う。
・当たり判定の順番
実際のプログラムにおいては当たり判定と移動処理が必要となります。移動処
理とは座標の更新であり、これが行われるごとに当たり判定も必要となります。
そう考えると自機ショットと敵の処理は以下のようになります。
ショットを移動→当たり判定→敵を移動→当たり判定
ですが、実際には下記のように当たり判定は1回で十分です。
自機ショットを移動→敵を移動→当たり判定
このように当たり判定を1回で済ませる場合には「すり抜け」と呼ばれる現象
に注意しなければなりません。
← 敵キャラ
+-----+
+---+ | | 自機ショットが右方向に、
|シ | |敵 | 敵キャラが左方向に進んでいる時、
+---+ +-----+
自機ショット→
← 敵キャラ
+-----+
| | +---+ 次の瞬間に敵キャラが自機ショットの左に、
|敵 | |シ | 自機ショットが敵キャラの右に進んでしまう
+-----+ +---+
自機ショット→
※この時、見た目上は当たったように感じられても「当たっていない」
事になってしまうのが「すり抜け」
敵キャラの速度を vx、自機ショットの速度を VX とすると以下の条件の時に
すり抜けが起こります。(速度は右向きを正とします)
ex-hx ex+hx
←vx +---+---+ 敵キャラ
ex
+-+-+ VX→ 自機ショット
SX SX+HX
↓
ex-hx ex+hx
+---+---+ 敵キャラ
ex
+-+-+ 自機ショット
SX SX+HX
vx+VX > hx+HX の時すり抜け発生
このように当たり判定を正確に行うためにはキャラクターの移動速度も的確に
設定する必要があります。
・点と矩形の当たり判定
もっと簡単に片方を(大きさを持たない数学上の)点として矩形と当たり判定
を行う方法もあります。当たり判定は単純に「点の座標が矩形領域の内部にある
か」を調べるだけになります。
ex-hx ex+hx
+---+---+ (sx>=ex-hx) かつ (sx<=ex+hx) なら当たり
ex
+ +
sx sx
この方法だと先ほどの矩形と矩形の判定と比較して加算が2回少なくて済みま
す。Y方向も当たり判定を行うと、合計4回加算が少なくなります。当たり判定
が点でも気にならないようなキャラクター、大量に当たり判定を行う必要がある
キャラクターの場合、この判定方式は有効です。シューティングゲームでは敵弾
と自機との当たり判定がこれに該当します。『男弾』『一本槍』共に敵弾と自機
の当たり判定はこの方法で行っています。先ほども述べたように速すぎるキャラ
クターだと「すり抜け」が起こる可能性があるのですが、幸い敵弾が遅くて多い
超弾幕スタイルのゲームですし、自機の速度もそれほど速くないため、この方法
が使えます。なお、自機ショットと敵の当たり判定の場合は自機ショットの速度
がかなり速いことを考慮して普通に矩形と矩形で行っています。
敵弾の当たり判定がどこまで小さくできるかは先ほどの式の通り敵弾と自機の
速度で決まります。そのため、速い敵弾は当たり判定を大き目に設定する必要が
ありますが、逆に遅い敵弾がどこまで当たり判定を小さくすることができるのか
を調べて、極限まで当たり判定を小さくしてみるのも面白いかもしれません。
・高速化の工夫
m 個のキャラクターと n 個のキャラクターの当たり判定を行う場合、m×n 回
の当たり判定が必要となります。これは相当重い処理なので様々な手段を用いて
高速化する必要があります。これに関しては有史以来多くの人が様々な方法を考
えて来たようですが、現時点では決定打といわれるような方法は無いようです。
やはり当たり判定は真面目に m×n 回行う必要があるわけですが、それでも若干
テクニックは存在します。
・当たり判定は2フレームに1回行う
特に自機と敵弾の当たり判定等、大量の判定を行う時の非常手段。こ
の方法だと敵弾の「すり抜け」が発生する危険がありますが、敵弾と自
機が十分遅い場合は問題ありません(すり抜けが起こる条件については
先ほどの説明を参照の事。
余談ですが、自機と弾の判定がおかしくなって自機が死なない事があっ
てもそれほど苦情は来ないようです(が、自機のショットが敵に当たら
ないような事があったら苦情殺到でしょう)。
・当たり判定を全て同じ大きさにする
敵弾の当たり判定は種類ごとに p->hx のようにワークに持たせる事
になるのでしょうが、「全ての敵弾の当たり判定を同じにする」とこれ
を変数に代入せず定数で持たせる事ができるため高速化できます。
変数)
if (p->x >= e->x - e->hx)
定数)
if (p->x >= e->x - 2)
ただ、これはゲームデザインにも関わってくるため採用するかどうか
は熟慮する必要があります。
・ボクセル分割
レイトレーシングで使われている高速化のテクニックです。これをシュー
ティングゲームに応用すると、「あらかじめ座標を大雑把に比較してあ
る程度の領域に入ったキャラクターのみ当たり判定を行う」という事に
なります。具体的に言うと、例えば画面を 4x4 の領域に分割し、同じ
領域にいるキャラクター同士のみの判定を行う…というものです。
(0,0)
+--+--+--+--+ ┐
| | | | | │64ドット
+--+--+--+--+ ┘
| | |A| |
+--+--+--+--+
| | | | |
+--+--+--+--+
| | |B| |
+--+--+--+--+
(255,255)
自機がAの位置にいる時はAの位置にいる敵弾のみと当たり判定を行
います。この場合、Bの位置にいる敵弾とは当たり判定を行いません。
これは自機と敵弾が同じ領域にいる確率は、違う領域にいる確率より低
いことを利用しています。
理論上は高速化が可能なのですが、現実的には「キャラクターがどの
領域にいるか」を判定するのに時間が掛かる(普通に判定するのと変わ
らない)ため効果が上がらないようです。
・無効弾判定による当たり判定のキャンセル
自機の位置/速度と敵弾の位置/速度によっては「絶対に自機に当た
らない敵弾(以下無効弾)」というものが出てきます。図を見て下さい。
図)
自機 敵弾
△→ ○→→
速度1 速度2
敵弾の速度が自機よりも速く、かつ方向が自機の反対方向だった場合、
この弾は絶対に自機に当たりません。そのため当たり判定も必要なくな
ります。
上記のように1次元で考えると簡単ですが、2次元で考えると難しい
ですね。「無効弾かどうかの判定」を高速に行う方法が見つかれば実用
化できるかもしれません。
▼ 処理の分散
リアルタイムゲームの処理は垂直同期に間に合うかどうかが重要である事を以
前述べました。処理内容が多い場合には処理落ちが起きても仕方がないのですが、
うまく処理を分散すれば垂直同期に間に合うかもしれません。その1例として炸
裂弾の処理を挙げてみます。
・炸裂弾の処理
『男弾』3面ボスの第2形態では32方向に炸裂弾を撃ってきます。これは敵弾
を発生する処理が1フレームの間に32回起こることを意味しています。そのため、
炸裂弾を撃つ時に瞬間的に処理落ちが起こる可能性があります。
これを回避するために、「最初のフレームでは16方向に敵弾を発生し、次のフ
レームで更に16方向に敵弾を発生する」という処理を行っています。こうするこ
とにより瞬間的な処理負荷を半分に減らす事に成功しています。
この方法に問題があるとすれば、炸裂弾の場合、ある1点から全ての敵弾が発
生した方が美しい円形になるのですが、1フレームの間に敵が動いてしまうと綺
麗な円形にならないという点が挙げられます。これに関しては炸裂弾を発射する
時に敵を静止させる(または移動速度を遅くする)、もしくは気にしないという
解決方法(?)があります。
他にも今回はグラフィック画面に背景を書く時に1回で描かず64回に分けて描
く、という事を行っています。詳しくは後ほど解説します。
(EOF)